---
title: 装饰器模式
description: 装饰器模式主要解决继承关系过于复杂的问题，通过组合来替代继承。主要作用是给原始类添加增强功能。可以对原始类嵌套使用多个装饰器；装饰器类需要跟原始类继承相同的抽象类或者接口。
---


## Java IO 类
Java IO 类库非常庞大和复杂，有几十个类，负责 IO 数据的读取和写入。如果对 Java IO 类做一下分类，我们可以从下面两个维度将它划分为四类。

<table>
    <caption>Java IO 类库划分</caption>
    <thead>
    <tr>
        <th ></th>
        <th width={'40%'}>字节流</th>
        <th width={'40%'}>字符流</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>
            输入流
        </td>
        <td>
            InputStream
        </td>
        <td>
            Reader
        </td>
    </tr>
    <tr>
        <td>
            输出流
        </td>
        <td>
            OutputStream
        </td>
        <td>
            Writer
        </td>
    </tr>
    </tbody>
</table>

针对不同的读取和写入场景，Java IO 又在这四个父类基础之上，扩展出了很多子类。具体如下所示：

<img src={'https://wkq-img.oss-cn-chengdu.aliyuncs.com/img/202409271729201.png'} alt="Java IO"/>

### 基于继承的设计方案
如果 InputStream 只有一个子类 FileInputStream 的话，那我们在 FileInputStream 基础上，再设计一个孙子类 BufferedFileInputStream, 也算是可以接受的，毕竟继承结构还算简单。但实际上，继承 InputStream 子类有很多。我们需要给每一个 InputStream 的子类，再继续派生支持缓存读取的子类。

除了支持缓存读取外，如果我们还需要对功能进行其它方面的增强，比如下面的 DataInputStream 类，支持按照基本数据类型（int、boolean、long 等）来读取数据。

```java
FileInputStream in = new FileInputStream("/user/test.txt");
DataInputStream din = new DataInputStream(in);
int data = din.readInt();
```

在这种情况下，如果我们继续按照继承的方式来实现的话，就需要再继续派生出 DataFileInputStream、DataPipedInputStream 等类。如果我们还需要既支持缓存，又支持按照基本类型读取数据的类，那就要再继续派生出 BufferedDataFileInputStream 、BufferedDataPipedInputStream 等 n 多类。这还只是附加了两个增强功能，如果我们需要附加更多的增强功能，那就会导致组合爆炸，类继承的结构将
无比复杂，代码既不好扩展，也不好维护。

### 基于装饰器的设计方案
针对继承结构过于复杂的问题，可以通过将继承关系改为组合关系来解决。下面的代码展示了 Java IO 的设计思路。

```java
public abstract class InputStream {
  //...
  public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
  }

  public int read(byte b[], int off, int len) throws IOException {
    //...
  }

  public long skip(long n) throws IOException {
    //...
  }

  public int available() throws IOException {
    return 0;
  }

  public void close() throws IOException {}

  public synchronized void mark(int readlimit) {}

  public synchronized void reset() throws IOException {
    throw new IOException("mark/reset not supported");
  }

  public boolean markSupported() {
    return false;
  }
}

public class BufferedInputStream extends InputStream {
  protected volatile InputStream in;

  protected BufferedInputStream(InputStream in) {
    this.in = in;
  }

  //...实现基于缓存的读数据接口...
}

public class DataInputStream extends InputStream {
  protected volatile InputStream in;

  protected DataInputStream(InputStream in) {
    this.in = in;
  }

  //...实现读取基本类型数据的接口
}
```
但是，装饰器并不是简单的“用组合替代继承”，从 Java IO 的设计上来看，装饰器模式相对于简单的组合关系，还有两个比较特殊的地方。

第一个比较特殊的点是：**装饰器类和原始类继承同样的父类，这样我们可以对原始类“嵌套”多个装饰器类。**

```java title='对FileInputStream嵌套了两个装饰器类：BufferedInputStream 和 DataInputStream，让它既支持缓存读取，又支持按照基本数据类型来读取数据'
InputStream in = new FileInputStream("/user/test.txt");
InputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);
int data = din.readInt();
```

第二个比较特殊的点是：**装饰器类是对功能的增强，这也是装饰器模式应用场景的一个重要特点**

符合“组合关系”这种代码结构的设计模式有很多，比如代理模式、桥接模式、适配器模式等。

[//]: # (TODO)

